import * as d3 from 'd3'
import * as d3Voronoi from 'd3-geo-voronoi'
import * as topojson from 'topojson'
export class MapChart {
  title = ''
  divId = ''
  data = ''
  jsonData = ''
  outlineData = ''
  // color = d3.scaleOrdinal(d3.schemePaired)
  color = function(i) {
    let mycolor = ['#949483', '#f47b7b', '#9f1f5c', '#ef9020', '#00af3e',
      '#85b7e2', '#29245c', '#ffd616', '#e5352b', '#e990ab', '#0081b4',
      '#96cbb3', '#91be3e', '#39a6dd']
    return mycolor[i % 14]
  }
  mapChart = {}
  constructor(divId) {
    this.divId = divId
  }

  setData(data) {
    this.data = data
  }

  setJsonData(data) {
    this.jsonData = data
  }

  setOutlineData(data) {
    this.outlineData = data
  }

  setAllCityData(data) {
    this.allcityData = data
  }

  getPath() {
    return this.mapChart.path
  }

  initChart() {
    this.mapChart.padding = {
      left: 0,
      right: 0,
      top: 0,
      bottom: 15
    }
    this.mapChart.divHeight = document.getElementById(this.divId).offsetHeight -
       this.mapChart.padding.top - this.mapChart.padding.bottom
    this.mapChart.divWidth = document.getElementById(this.divId).offsetWidth -
       this.mapChart.padding.left - this.mapChart.padding.right

    let chart = this.mapChart

    this.mapChart.zoom = d3.zoom()
      .scaleExtent([1, 8])
      .on('zoom', function(event) {
        const {transform} = event
        d3.select(this).attr('transform', transform)
      })
      .on('end', function(event) {
        const {transform} = event

        let city = d3.select(this).selectAll('.city')
          .filter(function(d) {
            if (parseInt(d3.select(this).style('opacity')) === 1) {
              return d
            }
          })

        city.select('circle')
          .transition()
          .duration(750)
          .attr('r', function(d) {
            return d._r / transform.k
          })

        if (transform.k >= 3) {
          city.select('text')
            .attr('transform', d => {
              return 'translate(' + (chart.projection([d.lon, d.lat])[0] + 1) +
                ',' + chart.projection([d.lon, d.lat])[1] + ')scale(' + (0.2 * 8 / transform.k) + ')'
            })
            .transition()
            .duration(750)
            .style('opacity', 1)
            .style('stroke', 'black')

          city.select('line')
            .transition()
            .duration(750)
            .style('stroke-width', function(d) {
              return d._strokeWidth / transform.k * 3
            })

          city.select('.comparison')
            .transition()
            .duration(750)
            .style('stroke-width', d => d._strokeWidth / transform.k * 2)

          d3.select(this).select('.cityMap').selectAll('path')
            .transition()
            .duration(750)
            .style('stroke-width', function(d) {
              return d._strokeWidth / transform.k * 2
            })
            .style('opacity', 1)

          d3.select(this).select('.map').selectAll('path')
            .transition()
            .duration(750)
            .style('opacity', 0)
        } else {
          city.select('text')
            .style('opacity', 0)

          city.select('.comparison')
            .transition()
            .duration(750)
            .style('stroke-width', d => d._strokeWidth / transform.k)

          d3.select(this).select('.map').selectAll('path')
            .transition()
            .duration(750)
            .style('stroke-width', function(d) {
              return d._strokeWidth / transform.k * 2
            })
            .style('opacity', 1)
        }

        d3.select(this).select('.voronois').selectAll('path')
          .transition()
          .duration(750)
          .style('stroke-width', function(d) {
            return d._strokeWidth / transform.k
          })

        d3.select(this).selectAll('.concentric')
          .transition()
          .duration(750)
          .style('stroke-width', function(d) {
            return d._strokeWidth / transform.k
          })
      })

    this.mapChart.svg = d3.select('#svg_mapChart')
      .attr('width', this.mapChart.divWidth + this.mapChart.padding.left + this.mapChart.padding.right)
      .attr('height', this.mapChart.divHeight + this.mapChart.padding.top + this.mapChart.padding.bottom)
      .attr('class', 'svg_mapChart')
      .attr('id', 'mapChart')
      .style('background-color', '#FAFBFC')
      .append('g')
      .attr('class', 'zoom')
      .call(this.mapChart.zoom)

    this.mapChart.projection = d3.geoMercator()
      .center([105, 39])
      .scale(Math.min(this.mapChart.divHeight, this.mapChart.divWidth) * 1.25)
      .translate([this.mapChart.divWidth / 2, this.mapChart.divHeight / 2])

    this.mapChart.path = d3.geoPath()
      .projection(this.mapChart.projection)

    this.mapChart.voronoi = d3Voronoi.geoVoronoi()
      .x(d => d.lon)
      .y(d => d.lat)
  }

  draw() {
    this.initChart()
    let features = this.jsonData // topojson.feature(this.jsonData, this.jsonData.objects['China'])
    this.drawMap(features)
    this.renderCityMap(this.allcityData)
    // let merge = topojson.merge(this.jsonData, this.jsonData.objects['China'].geometries.filter(function (d) {
    //   return d
    // }))
    // this.renderOutline(merge)
    this.renderCityPoint()
    this.renderVoronoi()
    this.drawLegend()
  }
  /**
   * 绘制背景地图
   */
  drawMap(features) {
    let chart = this.mapChart

    // this.mapChart.projection
    //   .fitExtent([[0, 0], [this.mapChart.divWidth, this.mapChart.divHeight]], features)

    let map = chart.svg.append('g')
      .attr('class', 'map')

    let paths = map.selectAll('.path')
      .data(features.features)
      .enter()
      .append('g')
      .attr('class', 'path')

    paths.append('path')
      .attr('d', chart.path)
      .style('stroke', 'white')
      .style('stroke-width', d => {
        d._strokeWidth = 1
        return 1
      })
      .style('fill', '#ddd') // (_, i) => this.color(i)
      .style('opacity', 1)

    let islands = paths.filter(d => {
      return d.properties.name === ''
    })
      .attr('id', 'islands')

    islands.select('path')
      .attr('transform', () => {
        return 'scale(' + 0.5 + ')translate(' + (this.mapChart.divWidth + 200) + ',' +
        (this.mapChart.divHeight - 200) + ')'
      })
      .style('stroke', 'black')

    let gIslands = document.getElementById('islands').getBBox()
    islands.append('rect')
      .attr('x', gIslands['x'])
      .attr('y', gIslands['y'])
      .attr('width', gIslands['width'])
      .attr('height', gIslands['height'])
      .style('stroke', 'black')
      .style('stroke-width', 1)
      .style('fill', 'none')

    // paths.append('text')
    //   .attr('transform', d => 'translate(' + chart.path.centroid(d) + ')')
    //   .text(d => d.properties.name)
    //   .attr('font-size', 8)
  }

  renderCityMap(allCity) {
    let chart = this.mapChart

    let map = chart.svg.append('g')
      .attr('class', 'cityMap')

    let paths = map.selectAll('.path')
      .data(allCity.features)
      .enter()
      .append('g')
      .attr('class', 'path')

    paths.append('path')
      .attr('d', chart.path)
      .style('stroke', 'white')
      .style('stroke-width', d => {
        d._strokeWidth = 1
        return 1
      })
      .style('fill', '#ddd')
      .style('opacity', 0)
  }
  /**
   * 添加voronoi图的边界
   * @param {*} outline
   */
  renderOutline(outline) {
    let goutline = this.mapChart.svg.selectAll('.outline')
      .data([outline])
      .enter()
      .append('g')
      .attr('class', 'outline')

    goutline.append('clipPath')
      .attr('id', 'clipPath-0')
      .append('path')
      .attr('d', this.mapChart.path)
      .attr('id', 'outline-0')
      .style('fill', 'none')
      .style('stroke', 'black')
      .style('stroke-width', 1)
  }
  /**
   * 绘制每个城市的中心位置
   */
  renderCityPoint() {
    let gCities = this.mapChart.svg.append('g')
      .attr('class', 'cities')
      .selectAll('.city')
      .data(this.data)
      .enter()
      .append('g')
      .attr('class', 'city')
      .attr('id', d => d.name)
      .style('stroke', 'red')
      .style('fill', 'red')

    gCities.append('circle')
      .attr('cx', d => {
        d._oldX = this.mapChart.projection([d.lon, d.lat])[0]
        d._oldY = this.mapChart.projection([d.lon, d.lat])[1]
        d._r = 1
        return this.mapChart.projection([d.lon, d.lat])[0]
      })
      .attr('cy', d => this.mapChart.projection([d.lon, d.lat])[1])
      .attr('r', 1)

    gCities.append('text')
      .attr('transform', d => 'translate(' + d._oldX +
        ',' + d._oldY + ')')
      .text(d => d.name)
      .attr('font-size', 8)
      .style('opacity', 0)
  }
  /**
   * 绘制voronoi图
   */
  renderVoronoi() {
    this.mapChart.voronoi = d3Voronoi.geoVoronoi()
      .x(d => d.lon)
      .y(d => d.lat)
    let vdata = this.mapChart.voronoi(this.data)

    let gVoronoi = this.mapChart.svg.append('g')
      .attr('class', 'voronois')
      .attr('clip-path', 'url(#clipPath-0)')

    gVoronoi.selectAll('.voronoi')
      .data(vdata.polygons().features)
      .enter()
      .append('path')
      .attr('d', this.mapChart.path)
      .on('click', (event, d) => {
        this.renderCityWind(d.properties.site.name)
      })
      .style('fill', d => this.color(d.properties.site.category))
      .style('stroke', 'black')
      .style('stroke-width', 1)
      .style('opacity', 0.5)
      .append('title')
      .text(d => d.properties.site.name)
  }

  drawLegend() {
    let category = ['长江中游城市群', '哈长城市群', '成渝城市群', '长江三角洲城市群', '中原城市群',
      '北部湾城市群', '关中平原城市群', '呼包鄂榆城市群', '兰西城市群', '粤港澳大湾区', '京津冀城市群',
      '辽中南城市群', '山东半岛城市群', '海峡西岸城市群']
    let legendWidth = 20
    let legendHeight = 10
    let yStart = this.mapChart.divHeight - legendHeight * category.length -
      this.mapChart.padding.bottom

    let legends = this.mapChart.svg
      .append('g')
      .attr('class', 'legends')

    let legend = legends.selectAll('.legend')
      .data(category)
      .enter()
      .append('g')
      .attr('class', 'legend')

    legend.append('rect')
      .attr('x', 10)
      .attr('y', (_, i) => {
        return yStart + legendHeight * i
      })
      .attr('width', legendWidth)
      .attr('height', legendHeight - 3)
      .style('fill', (_, i) => this.color(i))
      .style('opacity', 0.5)

    legend.append('text')
      .attr('x', 10 + legendWidth * 1.2)
      .attr('y', (_, i) => (yStart + 1 + legendHeight * i + legendHeight / 2))
      .text(String)
      .attr('font-size', 10)
  }
  /**
   * 获取选定区域的边界
   * @param {*} cities
   */
  getBoundary(cities) {
    let merge = topojson.merge(this.jsonData, this.jsonData.objects['China'].geometries.filter(function(d) {
      if (cities.indexOf(d.properties.name) !== -1) {
        return d
      }
    }))
    this.renderBoundary(merge)
  }

  renderBoundary(boundary) {
    let gboundary = this.mapChart.svg.append('g')
      .attr('class', 'boundary')

    gboundary.selectAll('.provinceBoundary')
      .data([boundary])
      .enter()
      .append('path')
      .attr('d', this.mapChart.path)
      .style('stroke', 'red')
      .style('stroke-width', 1)
      .style('fill', (_, i) => this.color(i))
      .style('opacity', 0.5)
  }

  renderCityWind(city) {
    let gCities = this.mapChart.svg.select('.cities').selectAll('.city')
    gCities.style('opacity', 0)
    let data = ''
    let targetCity = gCities.filter(d => {
      if (d.name === city) {
        data = JSON.parse(JSON.stringify(d))
        return d
      }
    })
      .style('opacity', 1)
      .style('fill', 'red')
      .style('stroke', 'red')

    let containCity = gCities.filter(d => {
      let distance = this.geoDistance(d.lon, d.lat, data.lon, data.lat)
      if (distance < 200 && d.name !== city) {
        return d
      }
    })

    containCity.style('opacity', 1)
    containCity.style('fill', 'blue')
    containCity.style('stroke', 'blue')

    // this.renderWind(containCity)
    this.renderContainCompa(containCity)
    this.renderComparison(data)

    this.mapChart.svg.transition()
      .duration(750)
      .call(this.mapChart.zoom.transform,
        d3.zoomIdentity.translate(this.mapChart.divWidth / 2, this.mapChart.divHeight / 2)
          .scale(7)
          .translate(-targetCity.select('circle').attr('cx'), -targetCity.select('circle').attr('cy')))
  }

  renderWind(city) {
    let r = 6
    city.append('line')
      .attr('x1', d => this.mapChart.projection([d.lon, d.lat])[0])
      .attr('y1', d => this.mapChart.projection([d.lon, d.lat])[1])
      .attr('x2', d => {
        return this.mapChart.projection([d.lon, d.lat])[0] + r * Math.cos(90 - d['风向'])
      })
      .attr('y2', d => this.mapChart.projection([d.lon, d.lat])[1] + r * Math.sin(90 - d['风向']))
      .style('stroke', 'orange')
      .style('stroke-width', d => {
        d._strokeWidth = 1
        return 1
      })
  }

  renderComparison(data) {
    let gConcentric = this.mapChart.svg.append('g')
      .attr('class', 'gConcentric')

    let radius = 200 // km
    let circle = d3.geoCircle()
      .center(d => {
        return [d.lon, d.lat]
      })
      .radius(radius / (6371 * Math.PI * 2) * 360)

    let bounds = this.mapChart.path.bounds(circle(data))
    let r = (bounds[1][0] - bounds[0][0]) / 2

    // gConcentric.selectAll('.concentric')
    //   .data([data])
    //   .enter()
    //   .append('path')
    //   .attr('class', 'concentric')
    //   .attr('d', d => {
    //     console.log(d)
    //     const bounds = this.mapChart.path.bounds(circle(d))
    //     // console.log(bounds)
    //     // console.log((bounds[1][0] - bounds[0][0]), (bounds[1][1] - bounds[0][1]))
    //     r = bounds[1][0] - bounds[0][0]
    //     return this.mapChart.path(circle(d))
    //   })
    //   .style('fill', 'none')
    //   .style('stroke', 'orange')
    //   .style('stroke-width', 2)

    gConcentric.selectAll('.concentric')
      .data([data])
      .enter()
      .append('circle')
      .attr('class', 'concentric')
      .attr('cx', d => this.mapChart.projection([d.lon, d.lat])[0])
      .attr('cy', d => this.mapChart.projection([d.lon, d.lat])[1])
      .attr('r', r)
      .style('fill', 'none')
      .style('stroke', 'orange')
      .style('stroke-width', d => {
        d._strokeWidth = 2
        return 2
      })
  }

  renderContainCompa(containCity) {
    let arc = d3.arc()
      .outerRadius(d => d.outerRadius)
      .innerRadius(d => d.outerRadius)
      .startAngle(Math.PI)

    containCity.append('path')
      .attr('d', d => {
        let minDis = Number.MAX_VALUE
        containCity.each(o => {
          if (d !== o) {
            let distance = Math.sqrt(Math.pow((d._oldX - o._oldX), 2) + Math.pow((d._oldY - o._oldY), 2))
            if (distance < minDis) {
              minDis = distance
            }
          }
        })
        let _d = JSON.parse(JSON.stringify(d))
        _d.startAngle = Math.PI
        _d.endAngle = Math.PI * 2
        _d.outerRadius = minDis / 2
        return arc(_d)
      })
      .attr('class', 'comparison')
      .attr('transform', d => {
        return 'translate(' + d._oldX + ',' + d._oldY + ')'
      })
      .style('fill', 'none')
      .style('stroke', 'black')
      .style('stroke-width', d => {
        d._strokeWidth = 1
        return 1
      })
  }
  /**
   * 计算两个点的距离，单位为km
   * @param {int} lng1
   * @param {int} lat1
   * @param {int} lng2
   * @param {int} lat2
   * @returns 两点之间的距离
   */
  geoDistance(lng1, lat1, lng2, lat2) {
    let radLat1 = rad(lat1)
    let radLat2 = rad(lat2)
    let a = radLat1 - radLat2
    let b = rad(lng1) - rad(lng2)
    let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
        Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
    s = s * 6378.137 // EARTH_RADIUS;
    s = Math.round(s * 10000) / 10000 // 输出为公里

    function rad(d) {
      return d * Math.PI / 180.0
    }
    return s
  }

  renderSelectCity(features, city) {
    let chart = this.mapChart

    let projection = d3.geoMercator()
      .fitExtent([[0, 0], [chart.divWidth, chart.divHeight]], features)

    let path = d3.geoPath()
      .projection(projection)

    let map = chart.svg.append('g')
      .attr('class', 'selectCity')
      .style('opacity', 1)

    let paths = map.selectAll('.path')
      .data(features.features)
      .enter()
      .append('g')
      .attr('class', 'path')

    paths.append('path')
      .attr('d', path)
      .style('stroke', 'white')
      .style('stroke-width', 1)
      .style('fill', '#ddd')
      .style('opacity', 1)
  }
}
